home *** CD-ROM | disk | FTP | other *** search
- /* See the file Distribution for distribution terms.
- Copyright (c) 1994-1995 Ari Halberstadt */
-
- /* A simple program to test my threads library. First a test is run using
- Apple's Thread Manager. Then, the same test is run using my Thread Library.
- In each test, a dialog is displayed with two counters, each incremented in
- its own thread. The display of the counters is updated once a second from
- a third thread. The "count1" line contains the value of the first counter,
- the "count2" line contains the value of the second counter. The "elapsed"
- line contains the number of ticks between updates (should be close to 60
- ticks). The "remaining" line shows the number of seconds until the test
- is finished (about 45 seconds). Click the "Quit" button to quit the program.
-
- 950531 aih - removed Stop button
- 950403 aih - uses new interface that makes Thread Library act like
- Thread Manager
- - any number of test dialogs can be created (no globals)
- 940907 aih - uses ThreadEndAll to dispose of all threads
- 940315 aih - fixed handling of update events and window dragging;
- the previous releases of this test application just
- barely worked by a lucky accident
- - added debugging commands for TMON and MacsBug
- - added a few Gestalt utility routines
- - remaining time is shown as seconds instead of ticks
- - increased test time from 30 seconds to 45 seconds
- 940301 aih - removed test for system version
- 940225 aih - uses WaitNextEvent instead of GetNextEvent to demonstrate
- use of ThreadYieldInterval; had to add a bunch of code to
- test for presence of WNE trap
- 940217 aih - release 1.0d2.1
- - added test using Thread Manager
- - added stop button
- - added SetPort once every time through event loop; thanks
- to Matthew Xavier Mora <mxmora@unix.sri.com> for suggesting
- this.
- - added note about update problem
- - added a couple of dialog utility functions
- 940217 aih - release 1.0d2
- - removed exception handlers
- 940214 aih - thread serial numbers are now used when disposing of the
- threads to prevent the possibility of disposing of a
- thread more than once
- - added some more comments to make it easier to follow this
- program
- 940212 aih - uses ThreadPtr instead of ThreadHandle
- - uses IsDialogEvent/DialogSelect instead of ModalDialog,
- and a modeless dialog is used
- - set size resource flags so it's multifinder aware and
- can be run in the background
- - with the above changes, ran about 3 times faster on my mac;
- i think most of the slowness of the previous version was
- due to the overhead of ModalDialog and EventAvail.
- - added error alert
- 940210 aih - created */
-
- #include <Desk.h>
- #include <Dialogs.h>
- #include <Events.h>
- #include <Fonts.h>
- #include <GestaltEqu.h>
- #include <LowMem.h>
- #include <Memory.h>
- #include <Menus.h>
- #include <QuickDraw.h>
- #include <SegLoad.h>
- #include <OSEvents.h>
- #include <OSUtils.h>
- #include <Resources.h>
- #include <TextUtils.h>
- #include <ToolUtils.h>
- #include <Threads.h>
- #include <Traps.h>
- #include "ThreadLibraryManager.h"
-
- /*----------------------------------------------------------------------------*/
- /* global definitions and declarations */
- /*----------------------------------------------------------------------------*/
-
- /* When DEBUGGER_CHECKS is defined as 1 then commands for a low-level
- debugger (either TMON or MacsBug) are executed on startup to enable
- useful debugging options:
-
- - the integrity of the heap will be checked after every trap that
- could move or purge memory
-
- - if discipline is installed, then it will be enabled.
-
- While these tests are very useful when debugging, they will make the
- test application run very slowly. Since the THINK C debugger intercepts
- the DebugStr trap, the TMON commands can only be executed if a
- compiled application is run without the THINK C debugger. */
- #ifndef DEBUGGER_CHECKS
- #define DEBUGGER_CHECKS (0)
- #endif
-
- #define DIALOG_ID (128)
- #define ERROR_ALERT_ID (256)
- #define METHOD_ALERT_ID (257)
- #define SECTICKS (60L)
- #define RUNTICKS (SECTICKS * 60L)
-
- /* dialog items */
- enum {
- iQuit = 5,
- iType,
- iCount1,
- iCount2,
- iElapsed,
- iRemaining,
- iLast
- };
-
- /* alert items */
- enum {
- iMethodThreadLibrary = 1,
- iMethodThreadManager
- };
-
- typedef OSErr (*NewThreadProcType)(ThreadStyle threadStyle, ThreadEntryProcPtr threadEntry, void *threadParam, Size stackSize, ThreadOptions options, void **threadResult, ThreadID *threadMade);
- typedef OSErr (*DisposeThreadProcType)(ThreadID threadToDump, void *threadResult, Boolean recycleThread);
-
- /* info for a test */
- typedef struct {
- DialogPtr dlg;
- long counter1;
- long counter2;
- ThreadID thread_counter1;
- ThreadID thread_counter2;
- ThreadID thread_display;
- DisposeThreadProcType dispose;
- } ThreadTestType;
-
- /* true if quitting program */
- static Boolean gQuit;
-
- /*----------------------------------------------------------------------------*/
- /* standard Macintosh initializations */
- /*----------------------------------------------------------------------------*/
-
- /* initialize application heap */
- static void HeapInit(long stack, short masters)
- {
- SetApplLimit(GetApplLimit() - stack);
- MaxApplZone();
- while (masters-- > 0)
- MoreMasters();
- }
-
- /* initialize managers */
- static void ManagersInit(void)
- {
- EventRecord event;
- short i;
-
- /* standard initializations */
- InitGraf((Ptr) &qd.thePort);
- InitFonts();
- InitWindows();
- InitMenus();
- TEInit();
- InitDialogs(0);
- FlushEvents(everyEvent, 0);
- InitCursor();
-
- /* so first window will be frontmost */
- for (i = 0; i < 4; i++)
- EventAvail(everyEvent, &event);
- }
-
- /*----------------------------------------------------------------------------*/
- /* functions for determining features of the operating environment */
- /*----------------------------------------------------------------------------*/
-
- /* return number of toolbox traps */
- static short TrapNumToolbox(void)
- {
- short result = 0;
-
- if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xAA6E, ToolTrap))
- result = 0x0200;
- else
- result = 0x0400;
- return(result);
- }
-
- /* return the type of the trap */
- static TrapType TrapTypeGet(short trap)
- {
- return((trap & 0x0800) > 0 ? ToolTrap : OSTrap);
- }
-
- /* true if the trap is available */
- static Boolean TrapAvailable(short trap)
- {
- TrapType type;
-
- type = TrapTypeGet(trap);
- if (type == ToolTrap) {
- trap &= 0x07FF;
- if (trap >= TrapNumToolbox())
- trap = _Unimplemented;
- }
- return(NGetTrapAddress(trap, type) != NGetTrapAddress(_Unimplemented, ToolTrap));
- }
-
- /* true if gestalt trap is available */
- static Boolean GestaltAvailable(void)
- {
- static Boolean initialized, available;
-
- if (! initialized) {
- available = TrapAvailable(0xA1AD);
- initialized = true;
- }
- return(available);
- }
-
- /* return gestalt response, or 0 if error or gestalt not available */
- static long GestaltResponse(OSType selector)
- {
- long response, result;
-
- response = result = 0;
- if (GestaltAvailable() && Gestalt(selector, &response) == noErr)
- result = response;
- return(result);
- }
-
- /* test bit in gestalt response; false if error or gestalt not available */
- static Boolean GestaltBitTst(OSType selector, short bit)
- {
- return((GestaltResponse(selector) & (1 << bit)) != 0);
- }
-
- /* true if the WaitNextEvent trap is available */
- static Boolean MacHasWNE(void)
- {
- static Boolean initialized;
- static Boolean wne;
-
- if (! initialized) {
- /* do only once for efficiency */
- wne = TrapAvailable(_WaitNextEvent);
- initialized = true;
- }
- return(wne);
- }
-
- /* true if TMON is installed */
- static Boolean MacHasTMON(void)
- {
- /* See "TMON Professional Reference", section 9.4.1 "Testing for
- Monitor Presence" (1990, ICOM Simulations, Inc). This only
- works with version 3.0 or later of TMON. */
- return(GestaltResponse('TMON') != 0);
- }
-
- /*----------------------------------------------------------------------------*/
- /* debug utilities */
- /*----------------------------------------------------------------------------*/
-
- /* Execute debugger commands to enable discipline and heap check
- on all traps in this application. These commands make the program
- run *very* slowly, but the commands are also very useful for
- finding bugs. */
- static void DebuggerChecksOn(void)
- {
- #if DEBUGGER_CHECKS
- if (MacHasTMON()) {
- /* use TMON */
- DebugStr((StringPtr) "\p™traps /check=1");
- DebugStr((StringPtr) "\p™traps /purge=1");
- DebugStr((StringPtr) "\p™traps /scramble=1");
- DebugStr((StringPtr) "\p™traps set discipline heap ..:inThisapp");
- }
- else {
- /* assume MacsBug */
- DebugStr((StringPtr) "\p; dsca on; athca; g");
- }
- #endif /* DEBUGGER_CHECKS */
- }
-
- /* Execute debugger commands to disable the debug options installed
- on startup. */
- static void DebuggerChecksOff(void)
- {
- #if DEBUGGER_CHECKS
- DebugStr((StringPtr) "\p™traps clear ..//; dsca off; atc; g");
- #endif /* DEBUGGER_CHECKS */
- }
-
- /*----------------------------------------------------------------------------*/
- /* event utilities */
- /*----------------------------------------------------------------------------*/
-
- /* Call GetNextEvent or WaitNextEvent, depending on which one is available.
- The parameters to this function are identical to those to WaitNextEvent.
- If GetNextEvent is called the extra parameters are ignored. */
- static Boolean EventGet(short mask, EventRecord *event,
- long sleep, RgnHandle cursor)
- {
- Boolean result = false;
-
- if (MacHasWNE())
- result = WaitNextEvent(mask, event, sleep, cursor);
- else {
- SystemTask();
- result = GetNextEvent(mask, event);
- }
- if (! result) {
- /* make sure it's a null event, even if the system thinks otherwise, e.g.,
- some desk accessory events (see comment in TransSkell event loop) */
- event->what = nullEvent;
- }
- return(result);
- }
-
- /*----------------------------------------------------------------------------*/
- /* dialog utilities */
- /*----------------------------------------------------------------------------*/
-
- /* set the text of the dialog item */
- static void SetDText(DialogPtr dlg, short item, const Str255 str)
- {
- short type;
- Handle hitem;
- Rect box;
-
- GetDialogItem(dlg, item, &type, &hitem, &box);
- SetDialogItemText(hitem, str);
- }
-
- /* set the text of the dialog item to the number */
- static void SetDNum(DialogPtr dlg, short item, long num)
- {
- Str255 str;
-
- NumToString(num, str);
- SetDText(dlg, item, str);
- }
-
- /*----------------------------------------------------------------------------*/
- /* thread utilities */
- /*----------------------------------------------------------------------------*/
-
- /* yield to other threads */
- static void Yield(void)
- {
- YieldToAnyThread();
- TLMYieldToAnyThread();
- }
-
- /*----------------------------------------------------------------------------*/
- /* The thread entry point functions. Every thread other than the main thread
- has an entry point function. When the thread is first run the entry point
- function is called. When the entry point function returns the thread is
- disposed of. We run these threads in infinite loops, and call ThreadYield
- after each iteration to allow other threads to execute. The threads will
- be terminated when the application exits. */
- /*----------------------------------------------------------------------------*/
-
- /* Thread to increment a counter. This is an extremely inefficient
- method to increment a counter. Even though context switches for threads
- are fairly fast, they are still about 2 orders of magnitude slower than
- the few instructions needed to execute a loop. This thread is not meant
- to be efficient, and is merely meant to illustrate how threads
- can be used. In a real application you would want threads to do much
- more in each iteration. */
- static pascal void *thread_count(void *data)
- {
- long *counter = data;
-
- for (;;) {
- ++*counter;
- Yield();
- }
- return(NULL);
- }
-
- /* Thread to update the display of the counters in the dialog. */
- static pascal void *thread_display(void *data)
- {
- ThreadTestType *test = data;
- long wake;
- long ticks;
-
- for (;;) {
- SetDNum(test->dlg, iCount1, test->counter1);
- SetDNum(test->dlg, iCount2, test->counter2);
- ticks = TickCount();
- wake = TickCount() + SECTICKS;
- while (TickCount() < wake)
- Yield();
- SetDNum(test->dlg, iElapsed, TickCount() - ticks);
- }
- return(NULL);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Thread creation and destruction functions. For convenience, we create all
- of the threads in one function, and dispose of them in another function.
- Threads can be created at any time, though the main thread, which is
- created with ThreadBeginMain, must be the first thread created and the
- last thread disposed of. */
- /*----------------------------------------------------------------------------*/
-
- /* display error number in an alert */
- static void ErrorAlert(OSErr err)
- {
- Str255 str;
-
- NumToString(err, str);
- ParamText(str, (StringPtr) "\p", (StringPtr) "\p", (StringPtr) "\p");
- StopAlert(ERROR_ALERT_ID, NULL);
- }
-
- /* display an error if non-zero error and then exit */
- static void FailOSErr(OSErr err)
- {
- if (err) {
- ErrorAlert(err);
- ExitToShell();
- }
- }
-
- /* display an error if nil and then exit */
- static void FailNIL(void *p)
- {
- if (! p)
- FailOSErr(MemError() ? MemError() : memFullErr);
- }
-
- /* display an error if nil and then exit */
- static void FailNILRes(void *p)
- {
- if (! p)
- FailOSErr(ResError() ? ResError() : resNotFound);
- }
-
- /* callback for 68k inline function */
- static OSErr TMNewThread(ThreadStyle threadStyle, ThreadEntryProcPtr threadEntry, void *threadParam, Size stackSize, ThreadOptions options, void **threadResult, ThreadID *threadMade)
- {
- return(NewThread(threadStyle, threadEntry, threadParam, stackSize, options, threadResult, threadMade));
- }
-
- /* callback for 68k inline function */
- static OSErr TMDisposeThread(ThreadID threadToDump, void *threadResult, Boolean recycleThread)
- {
- return(DisposeThread(threadToDump, threadResult, recycleThread));
- }
-
- /* Create a new test. */
- static ThreadTestType *ThreadTestBegin(NewThreadProcType new, DisposeThreadProcType dispose,
- const unsigned char *prompt, short ntests)
- {
- const short offset = 20;
- ThreadTestType *test;
-
- test = (ThreadTestType *) NewPtrClear(sizeof(ThreadTestType));
- FailNIL(test);
- test->dispose = dispose;
- test->dlg = GetNewDialog(DIALOG_ID, NULL, (WindowPtr) -1);
- FailNILRes(test->dlg);
- FailOSErr(new( kCooperativeThread, thread_count, &test->counter1, 0,
- kCreateIfNeeded, NULL, &test->thread_counter1));
- FailOSErr(new( kCooperativeThread, thread_count, &test->counter2, 0,
- kCreateIfNeeded, NULL, &test->thread_counter1));
- FailOSErr(new( kCooperativeThread, thread_display, test, 0,
- kCreateIfNeeded, NULL, &test->thread_display));
- SetDText(test->dlg, iType, prompt);
- SetWRefCon(test->dlg, (long) test);
- MoveWindow(test->dlg,
- qd.screenBits.bounds.left + (ntests + 1) * offset,
- qd.screenBits.bounds.top + 2 * GetMBarHeight() + (ntests + 1) * offset, false);
- ShowWindow(test->dlg);
- return(test);
- }
-
- /* Dispose of the test. */
- static void ThreadTestDispose(ThreadTestType *test)
- {
- if (test) {
- if (test->dlg) DisposeDialog(test->dlg);
- if (test->thread_counter1) test->dispose(test->thread_counter1, NULL, false);
- if (test->thread_counter2) test->dispose(test->thread_counter2, NULL, false);
- if (test->thread_display) test->dispose(test->thread_display, NULL, false);
- DisposePtr((Ptr) test);
- }
- }
-
- /*----------------------------------------------------------------------------*/
- /* Running the test program. */
- /*----------------------------------------------------------------------------*/
-
- /* handle the event, return true when should exit the event loop */
- static Boolean DoEvent(EventRecord *event)
- {
- WindowPtr window; /* for handling window events */
- DialogPtr dlgHit; /* dialog for which event was generated */
- short itemHit; /* item selected from dialog */
- Rect dragRect; /* rectangle in which to drag windows */
- ThreadTestType *test;
-
- switch (event->what) {
- case mouseDown:
- switch (FindWindow(event->where, &window)) {
- case inDrag:
- dragRect = (**GetGrayRgn()).rgnBBox;
- DragWindow(window, event->where, &dragRect);
- break;
- case inSysWindow:
- SystemClick(event, window);
- break;
- case inContent:
- if (window != FrontWindow()) {
- SelectWindow(window);
- event->what = nullEvent;
- }
- }
- break;
- case nullEvent:
- /* Yield to other threads only on null events, since whenever
- there's an event pending in the queue the main thread will
- be activated, so the call to Yield would be an expensive
- "no op". */
- Yield();
- break;
- }
-
- /* handle a dialog event */
- if (IsDialogEvent(event) && DialogSelect(event, &dlgHit, &itemHit)) {
-
- /* handle a click in one of the dialog's buttons */
- test = (ThreadTestType *) GetWRefCon(dlgHit);
- if (test) {
- switch (itemHit) {
- case iQuit:
- gQuit = true;
- break;
- }
- }
- }
- return(gQuit);
- }
-
- /* get and handle the next event; return true to exit event loop */
- static Boolean GetAndDoEvent(void)
- {
- EventRecord event;
-
- SetCursor(&qd.arrow);
- (void) EventGet(everyEvent, &event, 0, NULL);
- return(DoEvent(&event));
- }
-
- /* create the dialog and run the program */
- static void Run(ThreadTestType *tests[], short ntests)
- {
- long remainingUpdate; /* when to update the remaining time counter */
- long nextEvent; /* when to check for another event */
- long whenToStop; /* when to stop the test */
- short i;
-
- /* run until user stops or quits or we time out */
- remainingUpdate = nextEvent = 0;
- whenToStop = TickCount() + RUNTICKS;
- while (! GetAndDoEvent() && TickCount() < whenToStop) {
-
- /* every second update display of time remaining */
- if (TickCount() >= remainingUpdate) {
- for (i = 0; i < ntests; i++)
- SetDNum(tests[i]->dlg, iRemaining, (whenToStop - TickCount()) / SECTICKS);
- remainingUpdate = TickCount() + SECTICKS;
- }
-
- /* don't call WNE too often */
- while (TickCount() < nextEvent)
- Yield();
- nextEvent = TickCount() + 5;
- }
- }
-
- /* create and run several tests */
- static void CreateAndRun(NewThreadProcType new, DisposeThreadProcType dispose,
- const unsigned char *prompt)
- {
- const short ntests = 4;
- ThreadTestType *tests[4];
- short i;
-
- if (! gQuit) {
-
- /* create tests */
- for (i = 0; i < ntests; i++)
- tests[i] = ThreadTestBegin(new, dispose, prompt, i);
-
- /* run tests */
- Run(tests, ntests);
-
- /* dispose of tests */
- for (i = 0; i < ntests; i++)
- ThreadTestDispose(tests[i]);
- }
- }
-
- void main(void)
- {
- long threadsAttr;
- short method;
-
- /* enable extra checks */
- DebuggerChecksOn();
-
- /* standard initializations */
- HeapInit(0, 4);
- ManagersInit();
-
- /* select method */
- method = iMethodThreadLibrary;
- if (Gestalt(gestaltThreadMgrAttr, &threadsAttr) == noErr &&
- (threadsAttr & (1<<gestaltThreadMgrPresent)) != 0)
- {
- method = NoteAlert(METHOD_ALERT_ID, NULL);
- }
-
- /* run tests */
- switch (method) {
- case iMethodThreadLibrary:
- CreateAndRun(TLMNewThread, TLMDisposeThread, (StringPtr) "\pUsing Thread LIBRARY");
- break;
- case iMethodThreadManager:
- CreateAndRun(TMNewThread, TMDisposeThread, (StringPtr) "\pUsing Thread MANAGER");
- break;
- }
-
- /* disable extra checks */
- DebuggerChecksOff();
- }
-